package com.speedchina.common.rsa.aop;

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.speedchina.common.rsa.service.RSAService;
import com.speedchina.common.rsa.util.RSAUtils;
import com.speedchina.framework.utils.ReflectionUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.Validate;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author Winter
 */
@Aspect
@Order(5)
@Component
@Slf4j
public class RequestBodyRSAAspect {

    @Resource
    RSAService rsaService;
    @Autowired
    private ObjectMapper objectMapper;

    @Pointcut("@annotation(com.speedchina.common.rsa.aop.RequestRSA)")
    public void request() {
    }

    @Before("request()")
    public void doBefore(JoinPoint joinPoint) {

    }

    @Around("request()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = servletRequestAttributes.getRequest();
        String publicKey = request.getHeader("publicKey");
        if(StringUtils.isEmpty(publicKey)){
            log.error("{}","publicKey==null,未做处理");
            return joinPoint.proceed();
        }
        String privateKey = rsaService.getPrivateKey(publicKey);
        if(StringUtils.isEmpty(privateKey)){
            log.error("{}","privateKey==null,未做处理");
            return joinPoint.proceed();
        }
        Object[] args = joinPoint.getArgs();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Parameter[] p = method.getParameters();
        for (int i = 0; i < args.length; i++) {
            Object o = args[i];
            if (o instanceof ServletRequest || (o instanceof ServletResponse) || o instanceof MultipartFile) {
                args[i] = o.toString();
            }
        }
        for (int i = 0; i < p.length; i++) {
            Type type = p[i].getType();
            //此处执行RSA解密操作
            Object o = JSON.parseObject(JSON.toJSONString(args[i]), type);
            if (o instanceof String) {
                //做RSA解密操作
                args[i] = JSON.parseObject(RSAUtils.decryptByPrivateKey(privateKey,JSON.toJSONString(o)),String.class);
            } else if (o instanceof Object) {
                //遍历属性并且做RSA解密字段操作
                args[i] = dealObjectFileds(o,privateKey);
            }
        }
        rsaService.removePublicKeyPrivateKey(publicKey);
        try {
            return joinPoint.proceed(args);
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
    }

    @AfterReturning(returning = "ret", pointcut = "request()")
    public void doAfterReturning(Object ret) {
    }

    @SuppressWarnings("all")
    private StringBuilder handleParams(StringBuilder params, Object[] args, List paramNames) {
        try {
            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof Map) {
                    Set set = ((Map) args[i]).keySet();
                    List<Object> list = new ArrayList<Object>();
                    List<Object> paramList = new ArrayList<Object>();
                    for (Object key : set) {
                        list.add(((Map) args[i]).get(key));
                        paramList.add(key);
                    }
                    return handleParams(params, list.toArray(), paramList);
                } else {
                    if (args[i] instanceof Serializable) {
                        Class<?> aClass = args[i].getClass();
                        try {
                            aClass.getDeclaredMethod("toString", new Class[]{null});
                            // 如果不抛出 NoSuchMethodException 异常则存在 toString 方法 ，安全的 writeValueAsString ，否则 走 Object的 toString方法
                            params.append(" ").append(paramNames.get(i)).append(": ").append(objectMapper.writeValueAsString(args[i]));
                        } catch (NoSuchMethodException e) {
                            params.append(" ").append(paramNames.get(i)).append(": ").append(objectMapper.writeValueAsString(args[i].toString()));
                        }
                    } else if (args[i] instanceof MultipartFile) {
                        MultipartFile file = (MultipartFile) args[i];
                        params.append(" ").append(paramNames.get(i)).append(": ").append(file.getName());
                    } else {
                        params.append(" ").append(paramNames.get(i)).append(": ").append(args[i]);
                    }
                }
            }
        } catch (Exception ignore) {
            params.append("参数解析失败");
        }
        return params;
    }

    private Object dealObjectFileds(Object o,String privateKey) throws Exception {
        List<Field> fields = getAllFieldsList(o.getClass());
        for (int i = 0; i < fields.size(); i++) {
            // 设置字段可访问， 否则无法访问private修饰的变量值
            fields.get(i).setAccessible(true);
            Type type = fields.get(i).getType();
            String name = fields.get(i).getName();
            Object value = fields.get(i).get(o);
            if(value!=null&&!StringUtils.isEmpty(value.toString())){
                if(value instanceof String){
                    Object object = JSON.parseObject(RSAUtils.decryptByPrivateKey(privateKey,JSON.toJSONString(value)),String.class);
                    ReflectionUtils.setFieldValue(o,name,object);
                }
            }
        }
        return o;
    }

    public static List<Field> getAllFieldsList(final Class<?> cls) {
        Validate.isTrue(cls != null, "The class must not be null");
        final List<Field> allFields = new ArrayList<Field>();
        Class<?> currentClass = cls;
        while (currentClass != null) {
            final Field[] declaredFields = currentClass.getDeclaredFields();
            for (final Field field : declaredFields) {
                allFields.add(field);
            }
            currentClass = currentClass.getSuperclass();
        }
        return allFields;
    }
}
